import {CodeUtil} from "../../codeAdapter/CodeUtil";
import {Logger} from "../config/Logger";

interface Point {
    x: number;
    y: number;
}

interface Rect {
    x: number;
    y: number;
    width: number;
    height: number;
    id?: number;
}

class QuadTreeNode {
    public bounds: Rect;
    public objects: Rect[];
    public children: QuadTreeNode[];

    constructor(bounds: Rect) {
        this.bounds = bounds;
        this.objects = [];
        this.children = [];
    }

    public split() {
        const subWidth = this.bounds.width / 2;
        const subHeight = this.bounds.height / 2;
        const x = this.bounds.x;
        const y = this.bounds.y;

        this.children.push(new QuadTreeNode({
            x: x + subWidth,
            y: y,
            width: subWidth,
            height: subHeight
        }));
        this.children.push(new QuadTreeNode({
            x: x,
            y: y,
            width: subWidth,
            height: subHeight
        }));
        this.children.push(new QuadTreeNode({
            x: x,
            y: y + subHeight,
            width: subWidth,
            height: subHeight
        }));
        this.children.push(new QuadTreeNode({
            x: x + subWidth,
            y: y + subHeight,
            width: subWidth,
            height: subHeight
        }));
    }

    public insert(rect: Rect) {
        if (!this.contains(rect)) {
            return false;
        }

        if (this.objects.length < 4 && !CodeUtil.isNullOrZero(this.children?.length)) {
            this.objects.push(rect);
            return true;
        }

        if (!CodeUtil.isNullOrZero(this.children?.length)) {
            this.split();
            for (const obj of this.objects) {
                this.insert(obj);
            }
            this.objects = [];
        }

        for (const child of this.children) {
            if (child.insert(rect)) {
                return true;
            }
        }

        return false;
    }

    public query(point: Point): number[] {
        const result: number[] = [];

        if (!this.containsPoint(point)) {
            print(`包含点,返回结果`);
            return result;
        }

        for (const obj of this.objects) {
            if (this.containsPointInRect(point, obj)) {
                if (obj.id) {
                    result.push(obj.id);
                }
            }
        }

        for (const child of this.children) {
            result.push(...child.query(point));
        }

        print(`包含点,返回结果`);

        return result;
    }

    public delete(id: number): boolean {
        let index = -1;
        for (let i = 0; i < this.objects.length; i++) {
            if (this.objects[i].id === id) {
                index = i;
                break;
            }
        }

        if (index !== -1) {
            this.objects.splice(index, 1);
            return true;
        }

        for (const child of this.children) {
            if (child.delete(id)) {
                return true;
            }
        }

        return false;
    }

    public updateNode(rect: Rect): boolean {
        if (this.delete(rect.id)) {
            if (!this.insert(rect)) {
                Logger.toastError(`Failed to reinsert rectangle with id ${rect.id}`);
            }
            return true;
        }

        for (const child of this.children) {
            if (child.updateNode(rect)) {
                return true;
            }
        }

        return false;
    }


    private contains(rect: Rect): boolean {
        return rect.x >= this.bounds.x && rect.x + rect.width <= this.bounds.x + this.bounds.width
            && rect.y >= this.bounds.y && rect.y + rect.height <= this.bounds.y + this.bounds.height;
    }

    private containsPoint(point: Point): boolean {
        return point.x >= this.bounds.x && point.x <= this.bounds.x + this.bounds.width
            && point.y >= this.bounds.y && point.y <= this.bounds.y + this.bounds.height;
    }

    private containsPointInRect(point: Point, rect: Rect): boolean {
        return point.x >= rect.x && point.x <= rect.x + rect.width
            && point.y >= rect.y && point.y <= rect.y + rect.height;
    }
}

export class QuadTree {
    public root: QuadTreeNode;

    constructor(bounds: Rect) {
        this.root = new QuadTreeNode(bounds);
    }

    public insert(rect: Rect) {
        this.root.insert(rect);
    }

    public query(point: Point): number[] {
        return this.root.query(point);
    }

    public delete(rect: Rect): boolean {
        return this.root.delete(rect.id);
    }

    public update(rect: Rect): boolean {
        return this.root.updateNode(rect);
    }
}